All files / src/components providers.tsx

0% Statements 0/58
0% Branches 0/41
0% Functions 0/12
0% Lines 0/53

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                                                                                                                                                                                                                                         
'use client';
 
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState, useEffect } from 'react';
import { AuthProvider } from '@/contexts/AuthContext';
import { ErrorProvider } from '@/providers/ErrorProvider';
import { AuthErrorBoundary } from '@/components/common/AuthErrorBoundary';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import i18n from '@/lib/i18n';
 
const i18nWithReact = i18n as typeof i18n & { __reactI18nextReady?: boolean };
if (!i18nWithReact.__reactI18nextReady) {
  i18nWithReact.use(initReactI18next);
  i18nWithReact.__reactI18nextReady = true;
}
 
/**
 * Syncs i18n language with localStorage after hydration to avoid SSR mismatch.
 */
function useI18nSync() {
  const [isReady, setIsReady] = useState(false);
 
  useEffect(() => {
    const fallbackLanguage = 'en';
    const supportedLangs = ((i18n.options.supportedLngs as string[] | undefined) || [])
      .filter((lang) => lang && lang !== 'cimode');
 
    const normalizeLanguage = (input: string | null | undefined): string => {
      if (!input) return fallbackLanguage;
      const normalized = input.replace('_', '-').toLowerCase();
      const directMatch = supportedLangs.find((lang) => lang.toLowerCase() === normalized);
      if (directMatch) return directMatch;
      const base = normalized.split('-')[0];
      const baseMatch = supportedLangs.find((lang) => lang.toLowerCase() === base);
      return baseMatch || fallbackLanguage;
    };
 
    const savedLang = localStorage.getItem('iptv_language');
    const browserLang = typeof navigator !== 'undefined' ? navigator.language : null;
    const targetLanguage = normalizeLanguage(savedLang || i18n.language || browserLang);
 
    if (savedLang !== targetLanguage) {
      localStorage.setItem('iptv_language', targetLanguage);
    }
 
    if (i18n.language !== targetLanguage) {
      i18n.changeLanguage(targetLanguage).then(() => setIsReady(true));
      return;
    }
 
    setIsReady(true);
  }, []);
 
  return isReady;
}
 
interface HttpError {
  response?: {
    status: number;
  };
}
 
export function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: {
          queries: {
            staleTime: 60 * 1000, // 1 minute
            retry: (failureCount, error: unknown) => {
              // Don't retry on 4xx errors except 408, 429
              const httpError = error as HttpError;
              const status = httpError?.response?.status ?? 0;
              if (status >= 400 && status < 500) {
                // Never retry 401 errors (authentication issues)
                if (status === 401) {
                  return false;
                }
                if (status === 408 || status === 429) {
                  return failureCount < 2;
                }
                return false;
              }
              // Retry on 5xx errors and network errors
              return failureCount < 3;
            },
            retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)},
          mutations: {
            retry: (failureCount, error: unknown) => {
              // Don't retry mutations on 4xx errors
              const status = (error as any)?.response?.status ?? 0;
              if (status >= 400 && status < 500) {
                return false;
              }
              // Retry on 5xx errors and network errors
              return failureCount < 2;
            }}}})
  );
 
  // Sync language from localStorage after hydration
  const i18nReady = useI18nSync();
 
  if (!i18nReady) {
    return (
      <div className="min-h-screen flex items-center justify-center">
        <div className="text-sm text-slate-500">{i18n.t('common.loading')}</div>
      </div>
    );
  }
 
  return (
    <I18nextProvider i18n={i18n}>
      <QueryClientProvider client={queryClient}>
        <ErrorProvider>
          <AuthErrorBoundary>
            <AuthProvider>
              {children}
            </AuthProvider>
          </AuthErrorBoundary>
        </ErrorProvider>
      </QueryClientProvider>
    </I18nextProvider>
  );
}